package edu.whu.rose.clock.ssql.query;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.ListIterator;

import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.setup.GraphDB;
import edu.whu.rose.clock.setup.IndexEnvironment;
import edu.whu.rose.clock.ssql.index.EdgeIndexDB;
import edu.whu.rose.clock.ssql.index.VertexIndexDB;
import edu.whu.rose.clock.ssql.util.DoubleSequentialIntMap;
import edu.whu.rose.clock.ssql.util.Merger;
import edu.whu.rose.clock.ssql.util.Path;

public class QueryManager {

	private VertexIndexDB vidb;
	private EdgeIndexDB eidb;
	private GraphDB gdb;
	
	private QueryArrayList queries;
	private ArrayList<String> relationships;
	
	public QueryManager(VertexIndexDB vidb, EdgeIndexDB eidb) {
		this.vidb = vidb;
		this.eidb = eidb;
		
		gdb = new GraphDB();
		if (!(gdb.initEnv() & gdb.openDB())) {
			System.exit(1);
		}
	}
	
	public static void main(String[] args) {
		IndexEnvironment ie1 = new IndexEnvironment("F:/data/dbEnv3");
		ie1.openEnv();
		IndexEnvironment ie2 = new IndexEnvironment("F:/edge index db env");
		ie2.openEnv();
		VertexIndexDB vidb = new VertexIndexDB(ie1.getEnvironment());
		EdgeIndexDB eidb = new EdgeIndexDB(ie2.getEnvironment());
		QueryManager qm = new QueryManager(vidb, eidb);
		qm.init();
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		ArrayList<String> result = null;
		try {
			while (true) {
				System.out.println("Please input query:");
				String statement = br.readLine().trim();
				if (!statement.equals("exit")) {
					Date startTime = new Date();
					result = qm.naiveQueryProcess(statement);
					Date endTime = new Date();
					if (result != null) {
						Iterator<String> it = (qm.showFullResult(result)).iterator();
						while (it.hasNext()) {
							System.out.println(it.next());
							System.out.println();
						}
						System.out.println("Time exhausted: " + (endTime.getTime() - startTime.getTime()) + " ms");
						System.out.println("Number of results: " + result.size());
					}
					else {
						System.out.println("Sorry, no result is found.");
					}
				}
				else {
					break;
				}
			}
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		qm.close();
		ie1.closeEnv();
		ie2.closeEnv();
	}
	
	public void init() {
		if (!vidb.openDB() || !eidb.openDB()) {
			System.exit(1);
		}
	}
	
	public void close() {
		gdb.closeDB();
		vidb.closeDB();
		eidb.closeDB();
	}
	
	public ArrayList<String> naiveQueryProcess(String statement) {
		this.naiveQueryParse(statement);
		
		String leftType = null;
		Object left = null;
		
		ListIterator<Query> li = queries.listIterator();
		while (li.hasNext()) {
			Query query = li.next();
			if (query.getType().equals("entity")) {
				((EntityQuery)query).setResult(this.naiveEntityQuery(query.getStatement()));
				if (((EntityQuery)query).getResult() == null) {
					return null;
				}
				if (leftType == null) {
					leftType = "e";
					left = ((EntityQuery)query).getResult();
				}
				else if (leftType.equals("r")) {
					left = Merger.rightUnion((DoubleSequentialIntMap)left, ((EntityQuery)query).getResult());
				}
				else if (leftType.equals("p")) {
					left = Merger.rightUnion((Path)left, ((EntityQuery)query).getResult());
				}
			}
			else if (query.getType().equals("relationship")) {
				((RelationshipQuery)query).setResult(this.naiveEdgeQuery(query.getStatement()));
				if (((RelationshipQuery)query).getResult() == null) {
					return null;
				}
				if (leftType == null) {
					leftType = "r";
					left = ((RelationshipQuery)query).getResult();
				}
				else if (leftType.equals("e")) {
					leftType = "r";
					left = Merger.leftUnion((int[])left, ((RelationshipQuery)query).getResult());
				}
				else if (leftType.equals("r")) {
					leftType = "p";
					left = Merger.midUnion((DoubleSequentialIntMap)left, ((RelationshipQuery)query).getResult());
				}
				else if (leftType.equals("p")) {
					left = Merger.midUnion((Path)left, ((RelationshipQuery)query).getResult());
				}
			}
			else {
				System.out.println("Error: wrong query type \"" + query.getType() + "\"");
			}
		}
		
		ArrayList<String> result = new ArrayList<String>();
		if (leftType.equals("p")) {
			result = ((Path)left).output();
		}
		else if (leftType.equals("r")) {
			result = ((DoubleSequentialIntMap)left).output();
		}
		else if (leftType.equals("e")) {
			for (int i = 0; i < ((int[])left).length; i++) {
				result.add("" + ((int[])left)[i]);
			}
		}
		else {
			return null;
		}
		
		return result;
	}
	
	private void naiveQueryParse(String statement) {
		this.queries = new QueryArrayList();
		this.relationships = new ArrayList<String>();
		int numOfRelationship = 0;
		int numOfEntity = 0;
		if (statement != null) {
			String query = statement.trim();
			for (int left = query.indexOf("["); left != -1; left = query.indexOf("[")) {
				if (left > 0) {
					String str = query.substring(0, left).trim();
					queries.add(new RelationshipQuery(str));
					relationships.add(str);
					numOfRelationship++;
				}
				int right = query.indexOf("]");
				if (right != -1) {
					queries.add(new EntityQuery(query.substring(left + 1, right).trim()));
					numOfEntity++;
				}
				else {
					System.out.println("Error: parsing query statement \"" + statement + "\"");
				}
				query = query.substring(right + 1).trim();
			}
			if (query.length() > 0) {
				queries.add(new RelationshipQuery(query));
				relationships.add(query);
				numOfRelationship++;
			}
			this.queries.setNumOfEntity(numOfEntity);
			this.queries.setNumOfRelationship(numOfRelationship);
		}
	}
	
	public int[] naiveEntityQuery(String statement) {
		String[] segments = statement.split(",");
		int[][] sequences = new int[segments.length][];
		for (int i = 0; i < segments.length; i++) {
			String segment = segments[i].trim();
			if (segment.length() > 0) {
				if (segment.startsWith("(") && segment.endsWith(")")) {
					int pos = segment.indexOf(":");
					if (pos != -1) {
						String attribute = segment.substring(1, pos).trim().toLowerCase();
						String[] keywords = segment.substring(pos + 1, segment.length() - 1).trim().split(" ");
						for (int j = 0; j < keywords.length; j++) {
							sequences[i] = vidb.search(keywords[j].toLowerCase() + "@@" + attribute);
						}
					}
					else {
						System.out.println("Syntax Error: wrong predicate \"" + segment + "\"");
						return null;
					}
				}
				else {
					sequences[i] = vidb.search(segment.toLowerCase());
				}
			}
			else {
				System.out.println("Syntax Error: wrong statement \"" + statement + "\"");
				return null;
			}
		}
		
		return Merger.unionVertexSequences(sequences);
	}
	
	public DoubleSequentialIntMap naiveEdgeQuery(String statement) {
		DoubleSequentialIntMap result = new DoubleSequentialIntMap();
		String[] pairs = eidb.search(statement);
		if (pairs != null) {
			for (int i = 0; i < pairs.length; i++) {
				String pair = pairs[i].trim();
				if (pair.length() > 0) {
					int pos = pair.indexOf("/");
					if (pos != -1) {
						String row = pair.substring(0, pos).trim();
						String column = pair.substring(pos + 1).trim();
						try {
							result.doubleInsert(new Integer(row).intValue() + 1, new Integer(column).intValue() + 1);
						}
						catch (NumberFormatException ex) {
							ex.printStackTrace();
							System.err.println("Error: wrong edge");
						}
					}
					else {
						System.err.println("Error: wrong edge");
					}
				}
				else {
					System.err.println("Error: empty edge");
				}
			}
		}
		return result;
	}
	
	public ArrayList<String> showResult(ArrayList<String> result) {
		ArrayList<String> newResult = new ArrayList<String>();
		ListIterator<String> li = result.listIterator();
		while (li.hasNext()) {
			String path = li.next().trim();
			String newPath = "";
			int num = 0;
			for (int left = path.indexOf("-->"); left != -1; left = path.indexOf("-->")) {
				if (left > 0) {
					String str = path.substring(0, left).trim();
					
					newPath += str;
				}
				else {
					System.err.println("somethingwrong......");
				}
				String relationship = relationships.get(num);
				num++;
				newPath += "--" + relationship + "-->\n";
				path = path.substring(left + 3).trim();
			}
			if (path.length() > 0) {
				newPath += path;
			}
			else {
				System.err.println("somethingwrong......");
			}
			newResult.add(newPath);
		}
		return newResult;
	}
	
	public ArrayList<String> showFullResult(ArrayList<String> result) {
		ArrayList<String> newResult = new ArrayList<String>();
		ListIterator<String> li = result.listIterator();
		while (li.hasNext()) {
			String path = li.next().trim();
			String newPath = "";
			int num = 0;
			for (int left = path.indexOf("-->"); left != -1; left = path.indexOf("-->")) {
				if (left > 0) {
					String str = path.substring(0, left).trim();
					try {
						int vertex = new Integer(str).intValue();
						newPath += vertex + " " + gdb.get(vertex);
					}
					catch (UnsupportedEncodingException ex) {
						ex.printStackTrace();
						System.err.println(str);
					}
					catch (DatabaseException ex) {
						ex.printStackTrace();
						System.err.println(str);
					}
				}
				else {
					System.err.println("somethingwrong......");
				}
				String relationship = relationships.get(num);
				num++;
				newPath += "--" + relationship + "-->\n";
				path = path.substring(left + 3).trim();
			}
			if (path.length() > 0) {
				try {
					int vertex = new Integer(path).intValue();
					newPath += vertex + " " + gdb.get(vertex);
				}
				catch (UnsupportedEncodingException ex) {
					ex.printStackTrace();
					System.err.println(path);
				}
				catch (DatabaseException ex) {
					ex.printStackTrace();
					System.err.println(path);
				}
			}
			else {
				System.err.println("somethingwrong......");
			}
			newResult.add(newPath);
		}
		return newResult;
	}

}
